package me.zhengjie.modules.system.service.impl

import cn.hutool.core.util.ObjectUtil
import cn.hutool.core.util.StrUtil
import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.CriteriaQuery
import jakarta.persistence.criteria.Root
import jakarta.servlet.http.HttpServletResponse
import me.zhengjie.common.exception.BadRequestException
import me.zhengjie.common.exception.EntityExistException
import me.zhengjie.common.utils.*
import me.zhengjie.common.utils.FileUtil.downloadExcel
import me.zhengjie.modules.system.domain.*
import me.zhengjie.modules.system.domain.vo.MenuMetaVo
import me.zhengjie.modules.system.domain.vo.MenuVo
import me.zhengjie.modules.system.repository.MenuRepository
import me.zhengjie.modules.system.repository.UserRepository
import me.zhengjie.modules.system.service.MenuService
import me.zhengjie.modules.system.service.RoleService
import me.zhengjie.modules.system.service.dto.MenuDTO
import me.zhengjie.modules.system.service.dto.MenuQueryCriteria
import me.zhengjie.modules.system.service.mapper.MenuMapper
import org.apache.commons.lang3.StringUtils
import org.springframework.cache.annotation.CacheConfig
import org.springframework.cache.annotation.CacheEvict
import org.springframework.cache.annotation.Cacheable
import org.springframework.data.domain.Sort
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import java.io.IOException
import java.util.*
import java.util.function.Consumer
import java.util.stream.Collectors


@Service
@CacheConfig(cacheNames = ["menu"])
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = [Exception::class])
class MenuServiceImpl(
    private val userRepository: UserRepository,
    private val menuRepository: MenuRepository,
    private val menuMapper: MenuMapper,
    private val redisUtils: RedisUtil,
    private val roleService: RoleService
) : MenuService {
    @Cacheable(key = "'queryall_criteria'")
    override fun queryAll(criteria: MenuQueryCriteria, isQuery:Boolean): List<MenuDTO?>? {
        val sort = Sort.by(Sort.Direction.ASC, "menuSort")
        if (java.lang.Boolean.TRUE == isQuery) {
            criteria.pidIsNull = true
            val fields = QueryHelp.getAllFields(criteria.javaClass, ArrayList())
            for (field in fields) {
                //设置对象的访问权限，保证对private的属性的访问
                field.isAccessible = true
                val `val` = field[criteria]
                if ("pidIsNull" == field.name) {
                    continue
                }
                if (ObjectUtil.isNotNull(`val`)) {
                    criteria.pidIsNull = null
                    break
                }
            }
        }
        return menuMapper.toDto(menuRepository.findAll { root: Root<Menu?>, criteriaQuery: CriteriaQuery<*>?, criteriaBuilder: CriteriaBuilder ->
            QueryHelp.getPredicate(
                root,
                criteria,
                criteriaBuilder
            )
        })
    }


    @Cacheable(key = "'findbyid'+#p0")
    override fun findById(id: Long): MenuDTO? {
        val menu = menuRepository.findById(id).orElseGet { Menu() }
        ValidationUtil.isNull(menu!!.id, "Menu", "id", id)
        return menuMapper.toDto(menu)
    }
//    @Cacheable(key = "'findbyroles'")
//    override fun findByRoles(roles: List<RoleSmallDTO>): List<MenuDTO> {
//        val menus: MutableSet<Menu> = LinkedHashSet()
//        for (role in roles) {
//            val menus1: List<Menu?> = ArrayList(
//                menuRepository.findByRoles_IdAndTypeIsNotInOrderBySortAsc(role.id!!, Collections.singletonList(2)))
//
//            menus.addAll(menus1 as Collection<Menu>)
//        }
//        return menus.stream().map { entity: Menu? -> menuMapper.toDto(entity)!! }.collect(Collectors.toList())!!
//    }

    /**
     * 用户角色改变时需清理缓存
     * @param currentUserId /
     * @return /
     */
    @Cacheable(key = "'user:' + #p0")
    override fun findByUser(currentUserId: Long?): List<MenuDTO?>? {
        val roles = roleService.findByUsersId(currentUserId)
        val roleIds = roles!!.map { it!!.id }.toSet()
        val menus = menuRepository.findByRoleIdsAndTypeNot(roleIds, 2)
        return menus!!.map { menuMapper.toDto(it)!! }.toList()
    }
    @CacheEvict(allEntries = true)
    //override fun create(resources: Menu): MenuDTO? {
    override fun create(resources: Menu) {
        if (menuRepository.findByTitle(resources.title) != null) {
            throw EntityExistException(Menu::class.java, "title", resources.title!!)
        }
        if (StringUtils.isNotBlank(resources.componentName!!)) {
            if (menuRepository.findByComponentName(resources.componentName) != null) {
                throw EntityExistException(Menu::class.java, "componentName", resources.componentName!!)
            }
        }
        if (resources.iFrame!!) {
            if (!(resources.path!!.lowercase(Locale.getDefault())
                    .startsWith("http://") || resources.path!!.lowercase(Locale.getDefault()).startsWith("https://"))
            ) {
                throw BadRequestException("外链必须以http://或者https://开头")
            }
        }
        //return menuMapper.toDto(menuRepository.save(resources))
        menuRepository.save(resources)
        resources.subCount = 0
        updateSubCnt(resources.pid)
    }

    @CacheEvict(allEntries = true)
    override fun update(resources: Menu) {
        if (resources.id == resources.pid) {
            throw BadRequestException("上级不能为自己")
        }
        val menu = menuRepository.findById(resources.id!!).orElseGet { Menu() }
        ValidationUtil.isNull(menu!!.id, "Permission", "id", resources.id)
        if (resources.iFrame!!) {
            if (!(resources.path!!.lowercase(Locale.getDefault())
                    .startsWith("http://") || resources.path!!.lowercase(Locale.getDefault()).startsWith("https://"))
            ) {
                throw BadRequestException("外链必须以http://或者https://开头")
            }
        }
        var menu1 = menuRepository.findByTitle(resources.title)
        if (menu1 != null && menu1.id != menu.id) {
            throw EntityExistException(Menu::class.java, "name", resources.title!!)
        }
        if (StringUtils.isNotBlank(resources.componentName)) {
            menu1 = menuRepository.findByComponentName(resources.componentName)
            if (menu1 != null && menu1.id != menu.id) {
                throw EntityExistException(Menu::class.java, "componentName", resources.componentName!!)
            }
        }
        menu.title  = resources.title
        menu.component = resources.component
        menu.path = resources.path
        menu.icon = resources.icon
        menu.iFrame = resources.iFrame
        menu.pid = resources.pid
        menu.menuSort = resources.menuSort
        menu.cache = resources.cache
        menu.hidden = resources.hidden
        menu.componentName = resources.componentName
        menu.permission = resources.permission
        menu.type = resources.type
        menuRepository.save(menu)
    }

    override fun getChildMenus(menuList: MutableList<Menu?>?, menuSet: MutableList<Menu?>?): MutableList<Menu?>? {
        for (menu in menuList!!) {
            menuSet!!.add(menu)
            val menus: List<Menu> = menuRepository.findByPidOrderByMenuSort(menu!!.id!!) as List<Menu>
            if (menus != null && menus.size != 0) {
                getChildMenus(menus as MutableList<Menu?>?, menuSet!!)
            }
        }
        return menuSet
    }
//    override fun getDeleteMenus(menuList: List<Menu>, menuSet: MutableSet<Menu>): Set<Menu> {
//        // 递归找出待删除的菜单
//        for (menu1 in menuList) {
//            menuSet.add(menu1)
//            val menus = menuRepository.findByPid(menu1.id!!)
//            if (menus != null && menus.size != 0) {
//                getDeleteMenus(menus as List<Menu>, menuSet)
//            }
//        }
//        return menuSet as Set<Menu>
//    }

    @CacheEvict(allEntries = true)
    @Transactional(rollbackFor = [Exception::class])
    override fun delete(menuSet: Set<Menu>) {
        for (menu in menuSet) {
            roleService.untiedMenu(menu.id)
            menuRepository.deleteById(menu.id!!)
        }
    }
    override fun getMenus(pid: Long?): List<MenuDTO?>? {
        val menus: List<Menu?>?
        menus = if (pid != null && pid != 0L) {
            menuRepository.findByPidOrderByMenuSort(pid)
        } else {
            menuRepository.findByPidIsNullOrderByMenuSort()
        }
        return menuMapper.toDto(menus)
    }

    override fun getSuperior(menuDto: MenuDTO?, menus: MutableList<Menu?>): List<MenuDTO?>? {
        if (menuDto!!.pid == null) {
            menus.addAll(menuRepository.findByPidIsNullOrderByMenuSort()!!)
            return menuMapper.toDto(menus)
        }
        menus.addAll(menuRepository.findByPidOrderByMenuSort(menuDto.pid!!)!!)
        return getSuperior(findById(menuDto.pid!!), menus)
    }

//    @Cacheable(key = "'getmenutree'")
//    override fun getMenuTree(menus: List<Menu>): Any {
//        val list: MutableList<Map<String, Any>> = LinkedList()
//        menus.forEach(
//            Consumer { menu: Menu? ->
//                if (menu != null) {
//                    val menuList = menuRepository.findByPid(menu.id!!)
//                    val map: MutableMap<String, Any> = HashMap()
//                    map["id"] = menu.id
//                    map["label"] = menu.title!!
//                    if (menuList != null && menuList.size != 0) {
//                        map["children"] = getMenuTree(menuList as List<Menu>)
//                    }
//                    list.add(map)
//                }
//            }
//        )
//        return list
//    }


    override fun buildTree(menuDTOS: List<MenuDTO?>?): List<MenuDTO?>?{
        var trees: MutableList<MenuDTO?> = arrayListOf()
        val ids: MutableSet<Long> = HashSet()
        for (menuDTO in menuDTOS!!) {
            if (menuDTO!!.pid == 0L) {
                trees.add(menuDTO)
            }
            for (it in menuDTOS) {
                if (it!!.pid == menuDTO.id) {
                    if (menuDTO.children == null) {
                        menuDTO.children = ArrayList()
                    }
                    (menuDTO.children as MutableList<MenuDTO>).add(it)
                    ids.add(it.id!!)
                }
            }
        }
        //val map: MutableMap<String, Any> = HashMap()
        if (trees.size == 0) {
            trees = menuDTOS!!.filter { !ids.contains(it!!.id) } as MutableList
        }
        //map["content"] = trees
        //map["totalElements"] = menuDTOS.size
        //return map
        return trees
    }

    override fun buildMenus(menuDTOS: List<MenuDTO?>): List<MenuVo> {
        val list: MutableList<MenuVo> = LinkedList()
        menuDTOS.forEach(
            Consumer { menuDTO: MenuDTO? ->
                if (menuDTO != null) {
                    val menuDTOList = menuDTO.children
                    val menuVo = MenuVo()
                    menuVo.name =
                        if (ObjectUtil.isNotEmpty(menuDTO.componentName)) menuDTO.componentName else menuDTO.title
                    // 一级目录需要加斜杠，不然会报警告
                    menuVo.path = if (menuDTO.pid == 0L) "/" + menuDTO.path else menuDTO.path
                    menuVo.hidden = menuDTO.hidden
                    // 如果不是外链
                    if (!menuDTO.iFrame!!) {
                        if (menuDTO.pid == 0L) {
                            menuVo.component = if (StrUtil.isEmpty(menuDTO.component)) "Layout" else menuDTO.component
                        } else if (!StrUtil.isEmpty(menuDTO.component)) {
                            menuVo.component = menuDTO.component
                        }
                    }
                    menuVo.meta = MenuMetaVo(menuDTO.title, menuDTO.icon, menuDTO.cache)
                    if (menuDTOList != null && menuDTOList.size != 0) {
                        menuVo.alwaysShow = true
                        menuVo.redirect = "noredirect"
                        menuVo.children = buildMenus(menuDTOList)
                        // 处理是一级菜单并且没有子菜单的情况
                    } else if (menuDTO.pid == 0L) {
                        val menuVo1 = MenuVo()
                        menuVo1.meta = menuVo.meta
                        // 非外链
                        if (!menuDTO.iFrame!!) {
                            menuVo1.path = "index"
                            menuVo1.name = menuVo.name
                            menuVo1.component = menuVo.component
                        } else {
                            menuVo1.path = menuDTO.path
                        }
                        menuVo.name = null
                        menuVo.meta = null
                        menuVo.component = "Layout"
                        val list1: MutableList<MenuVo> = ArrayList()
                        list1.add(menuVo1)
                        menuVo.children = list1
                    }
                    list.add(menuVo)
                }
            }
        )
        return list
    }

    override fun findOne(id: Long): Menu {
        val menu = menuRepository.findById(id).orElseGet { Menu() }
        ValidationUtil.isNull(menu!!.id, "Menu", "id", id)
        return menu
    }

    @Throws(IOException::class)
    override fun download(menuDTOS: List<MenuDTO>, response: HttpServletResponse?) {
        val list: MutableList<Map<String?, Any?>?> = ArrayList()
        for (menuDTO in menuDTOS) {
            val map: MutableMap<String?, Any?> = LinkedHashMap()
            map["菜单名称"] = menuDTO.title
            map["菜单类型"] = if (menuDTO.type == 0) "目录" else if (menuDTO.type == 1) "菜单" else "按钮"
            map["权限标识"] = menuDTO.permission
            map["外链菜单"] = if (menuDTO.iFrame!!) "是" else "否"
            map["菜单可见"] = if (menuDTO.hidden!!) "否" else "是"
            map["是否缓存"] = if (menuDTO.cache!!) "是" else "否"
            map["创建日期"] = menuDTO.createTime
            list.add(map)
        }
        downloadExcel(list, response!!)
    }

    private fun updateSubCnt(menuId: Long?) {
        if (menuId != null) {
            val count: Int = menuRepository.countByPid(menuId)
            menuRepository.updateSubCntById(count, menuId)
        }
    }

    /**
     * 清理缓存
     * @param id 菜单ID
     */
    fun delCaches(id: Long) {
        // 清除用户缓存
        val users = userRepository.findByMenuId(id)
        redisUtils.delByKeys(CacheKey.MENU_USER, extractIds(users)!!)

        // 清除菜单缓存
        redisUtils.del(CacheKey.MENU_ID + id)

        // 清除角色缓存
        val roles = roleService.findInMenuId(java.util.List.of(id))
        redisUtils.delByKeys(CacheKey.ROLE_ID, extractIds(roles)!!)
    }

    private fun <T> extractIds(entities: List<T?>?): Set<Long?>? {
        return entities!!.stream()
            .map { entity: T? -> (entity as User?)!!.id as Long? }
            .collect(Collectors.toSet())
    }
}